/*
 * @Author: hongbin
 * @Date: 2022-08-06 09:36:04
 * @LastEditors: hongbin
 * @LastEditTime: 2022-11-01 15:32:02
 * @Description:射线控制器 避免每帧碰撞检测创建新的射线 复用指定设限减少three.js计算
 */
import { Vector3, Matrix4, Raycaster, BufferGeometry, Line, LineBasicMaterial } from "three";
import { acterScaleMultiple } from "../../constants";
import { randomColor, TCntrols } from "../helper";
import { acterWrap } from "../object";

const { PI } = Math;

export function RayControl(direction: Vector3, moveDown: number, near: number, far: number) {
    const rayCaster = new Raycaster();

    rayCaster.near = near;
    rayCaster.far = far * acterScaleMultiple;

    function setRay(origin: Vector3) {
        rayCaster.ray.origin = origin;
        rayCaster.ray.origin.y -= moveDown * acterScaleMultiple;
        rayCaster.ray.direction = direction;
        return rayCaster;
    }

    function intersectObjects(objects: THREE.Object3D[], recursive: boolean, origin: Vector3) {
        setRay(origin);
        return rayCaster.intersectObjects(objects, recursive);
    }

    return {
        setRay,
        intersectObjects,
    };
}

//通过矩阵旋转然后将变化反应到坐标上 来让坐标旋转来实现更改射线方向  绕y轴旋转 默认可以理解为朝前方 旋转90度后就朝向左方 这样来实现目标点在当前点的某个方向
const Matrixs = {
    forward: new Matrix4().makeRotationY(0),
    back: new Matrix4().makeRotationY((180 * PI) / 180),
    left: new Matrix4().makeRotationY((90 * PI) / 180),
    right: new Matrix4().makeRotationY((270 * PI) / 180),
};

let _dir = new Vector3();

function _setDirByRotate(_Matrix: Matrix4) {
    //将该向量乘以四阶矩阵m（第四个维度隐式地为1），并按角度进行划分。
    _dir.applyMatrix4(_Matrix);
    _dir.normalize();
}
/**
 * 射线返回类型
 */
type Intersection = THREE.Intersection<THREE.Object3D<THREE.Event>>[];

/**
 * 如果四个方位检测参数一致可使用一个数组保持射线即可 如需定制不同射线每个方位一个专用的射线组
 */

const forwardRayCasters = [RayControl(_dir, 0, 0, 3), RayControl(_dir, -5, 0, 3), RayControl(_dir, -10, 0, 3)];
const backRayCasters = [RayControl(_dir, 0, 0, 3), RayControl(_dir, -5, 0, 3), RayControl(_dir, -10, 0, 3)];
const leftRayCasters = [RayControl(_dir, 0, 0, 3), RayControl(_dir, -5, 0, 3), RayControl(_dir, -10, 0, 3)];
const rightRayCasters = [RayControl(_dir, 0, 0, 3), RayControl(_dir, -5, 0, 3), RayControl(_dir, -10, 0, 3)];

const RayCasters = {
    forward: forwardRayCasters,
    back: backRayCasters,
    left: leftRayCasters,
    right: rightRayCasters,
};

const addHelperLine = (y: number, z: number) => {
    const points = [];
    points.push(new Vector3(0, y, 0));
    points.push(new Vector3(0, y, z));

    const rayHelper3 = new Line(
        new BufferGeometry().setFromPoints(points),
        new LineBasicMaterial({
            color: randomColor(),
        })
    );
    acterWrap.add(rayHelper3);
};

// addHelperLine(0, 3 * acterScaleMultiple);
// addHelperLine(5 * acterScaleMultiple, 3 * acterScaleMultiple);
// addHelperLine(10, 3 * acterScaleMultiple);

/**
 * 方向检测
 */
export function directionCheck(
    controls: TCntrols,
    objects: THREE.Object3D[],
    origin: Vector3,
    direction: keyof typeof Matrixs
) {
    controls.getDirection(_dir);
    _setDirByRotate(Matrixs[direction]);
    const rayCasters = RayCasters[direction];
    const intersection: Intersection = [];

    for (let i = 0; i < rayCasters.length; i++) {
        const forwardRayCaster = rayCasters[i];
        const result = forwardRayCaster.intersectObjects(objects, false, origin);
        if (result.length) {
            intersection.push(...result);
            break;
        }
    }

    return intersection;
}

const frontOfFootRayCaster = RayControl(_dir, 0, 0, 4);
/**
 * 脚前方障碍检测
 */
export function frontOfFoot(
    controls: TCntrols,
    objects: THREE.Object3D[],
    direction: keyof typeof Matrixs,
    origin: Vector3
) {
    controls.getDirection(_dir);
    _setDirByRotate(Matrixs[direction]);
    const result = frontOfFootRayCaster.intersectObjects(objects, false, origin);
    return result;
}

const kneeRayCaster = RayControl(_dir, -5, 0, 4);
/**
 * 膝盖障碍检测
 */
export function kneeCollide(objects: THREE.Object3D[], origin: Vector3) {
    const result = kneeRayCaster.intersectObjects(objects, false, origin);
    return result;
}

const uphillRayCaster = RayControl(_dir, 0.25, 0, 5);
/**
 * 上坡辅助线检测
 */
export function uphillCollide(controls: TCntrols, objects: THREE.Object3D[]) {
    // console.log(uphillRayCaster.rayCaster);
    //不明原因 使用传参进来的位置和实际的不一致导致无法检测到物体，遂使用controls重新获取
    return uphillRayCaster.intersectObjects(objects, false, controls.getObject().position.clone());
}

/**
 * 另一种区别于矩阵旋转然后将变化应用到Vecter上的设置射线方向的方式
 */
//方向朝下的射线 检测脚底
const fallRayCaster = new Raycaster(
    new Vector3(0, 0, 0),
    new Vector3(0, -1, 0),
    0,
    //投射多长的距离 如果太短可能会因为下降高度大于射线长度检测不到
    8 * acterScaleMultiple
);

//方向朝上的射线 检测头顶
const upRayCaster = new Raycaster(
    new Vector3(0, 0, 0),
    new Vector3(0, 1, 0),
    0,
    //投射距离要恰当 否则跳落下时加上射线的高度都会满足碰撞的条件 多次触发处理方法
    2 * acterScaleMultiple
);

/**
 * 头上碰撞检测
 */
export function upCollide(origin: Vector3, upDown: number, objects: THREE.Object3D[]) {
    upRayCaster.ray.origin.copy(origin);
    //计算头上的碰撞 需要加上身高 和一点点检测距离 不然贴上了才知道会穿过障碍物
    upRayCaster.ray.origin.y += upDown;
    return upRayCaster.intersectObjects(objects, false);
}

/**
 * 脚下碰撞检测
 */
export function fallCollide(origin: Vector3, objects: THREE.Object3D[]) {
    fallRayCaster.ray.origin.copy(origin);
    //脚下
    return fallRayCaster.intersectObjects(objects, true);
}

/**
 * 陷入地下向上发射射线
 */
const fallIntoFloorRayCaster = new Raycaster(new Vector3(0, -1, 0), new Vector3(0, 1, 0), 0, 20 * acterScaleMultiple);

export function fallIntoFloorCollide(origin: Vector3, objects: THREE.Object3D[]) {
    fallIntoFloorRayCaster.ray.origin.copy(origin);
    return fallIntoFloorRayCaster.intersectObjects(objects, false);
}
